From ec87a295591eb762ffa214f9c0da9ed7c9485721 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Tue, 14 Apr 2020 00:18:46 -0400 Subject: Simplify further --- .drone.yml | 24 ---------- Dockerfile | 12 ++--- Makefile | 56 +++++----------------- README.md | 134 ++-------------------------------------------------- example-server.conf | 23 ++------- 5 files changed, 24 insertions(+), 225 deletions(-) delete mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index fc79de0..0000000 --- a/.drone.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -kind: pipeline -name: build - -steps: -- name: run tests - image: golang - commands: - - sleep 5 - - make dep - - make test - environment: - MYSQL_TEST: true - MYSQL_TEST_HOST: mysql-server - MYSQL_TEST_USER: root - -services: -- name: mysql-server - image: mysql - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: certs - ports: - - 3306 diff --git a/Dockerfile b/Dockerfile index b9ab104..e8f03ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,12 @@ FROM golang:latest as build -LABEL maintainer="nsheridan@gmail.com" WORKDIR /build COPY go.mod . COPY go.sum . RUN go mod download COPY . . -RUN CGO_ENABLED=1 GOOS=linux make install-cashierd +RUN make cashierd -FROM gcr.io/distroless/static -LABEL maintainer="nsheridan@gmail.com" -WORKDIR /cashier -COPY --from=build /go/bin/cashierd / -ENTRYPOINT ["/cashierd"] +FROM alpine:latest +WORKDIR / +COPY --from=build /build/cashierd / +CMD ["/cashierd"] diff --git a/Makefile b/Makefile index 278ed8d..1e398df 100644 --- a/Makefile +++ b/Makefile @@ -1,54 +1,20 @@ -CASHIER_CMD := ./cmd/cashier -CASHIERD_CMD := ./cmd/cashierd -SRC_FILES = $(shell find * -type f -name '*.go' -not -path 'vendor/*' -not -name 'a_*-packr.go') +SOURCES=$(shell find . -type f -name '*.go') VERSION_PKG := github.com/nsheridan/cashier/lib.Version VERSION := $(shell git describe --tags --always --dirty) +IMAGE_NAME=benburwell/cashier -STATIC_LINKER_FLAGS ?= -linkmode external -extldflags -static -w -GOOS ?= $(shell go env GOOS) -GOARCH ?= $(shell go env GOARCH) -CGO_ENABLED ?= $(shell go env CGO_ENABLED) +all: cashier cashierd -all: test build +cashier: $(SOURCES) + go build -o $@ -ldflags="-X $(VERSION_PKG)=$(VERSION)" ./cmd/$@ -test: - go test -coverprofile=coverage.txt -covermode=count ./... - go install -race $(CASHIER_CMD) $(CASHIERD_CMD) +cashierd: $(SOURCES) + go build -o $@ -ldflags="-X $(VERSION_PKG)=$(VERSION)" ./cmd/$@ -lint: dep - go vet ./... - go list ./... |xargs -L1 golint -set_exit_status - gofmt -s -d -l -e $(SRC_FILES) - $(MAKE) generate - @[ -z "`git status --porcelain`" ] || (echo "unexpected files: `git status --porcelain`" && exit 1) - -build: cashier cashierd -install: install-cashierd install-cashier -cashier: cashier-bin -cashierd: cashierd-bin - -generate: - go generate ./... - -%-bin: - CGO_ENABLED=$(CGO_ENABLED) GOARCH=$(GOARCH) GOOS=$(GOOS) go build -ldflags="-X $(VERSION_PKG)=$(VERSION) $(STATIC_LINKER_FLAGS)" ./cmd/$* -install-%: generate - CGO_ENABLED=$(CGO_ENABLED) GOARCH=$(GOARCH) GOOS=$(GOOS) go install -ldflags="-X $(VERSION_PKG)=$(VERSION) $(STATIC_LINKER_FLAGS)" ./cmd/$* - -docker-image: - docker build -f Dockerfile . +.PHONY: docker +docker: + docker build -t "$(IMAGE_NAME)" . +.PHONY: clean clean: rm -f cashier cashierd - -# usage: make migration name=whatever -migration: - go run ./generate/migration/migration.go $(name) - -dep: - go get -u golang.org/x/lint/golint - -version: - @echo $(VERSION) - -.PHONY: all build dep generate test cashier cashierd clean migration diff --git a/README.md b/README.md index 8387e5b..76953cd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Cashier -[![Build Status](https://travis-ci.org/nsheridan/cashier.svg?branch=master)](https://travis-ci.org/nsheridan/cashier) - - [Cashier](#cashier) - [How it works](#how-it-works) - [Installing](#installing) @@ -10,17 +8,9 @@ - [Server](#server) - [Client](#client) - [Configuration](#configuration) - - [server](#server-1) - - [database](#database) - - [auth](#auth) - - [Provider-specific options](#provider-specific-options) - - [ssh](#ssh) - - [aws](#aws) - - [vault](#vault) - [Usage](#usage) - [Using cashier client](#using-cashier-client) - [Configuring SSH](#configuring-ssh) - - [Revoking certificates](#revoking-certificates) - [Future Work](#future-work) - [Contributing](#contributing) @@ -88,103 +78,20 @@ Note: Cashier has only been tested on macOS and Linux. # Configuration Configuration is divided into different sections: `server`, `auth`, `ssh`, and `aws`. -## A note on files: -For any option that takes a file path as a parameter (e.g. SSH signing key, TLS key, TLS cert), the path can be one of: - -- A relative or absolute filesystem path e.g. `/data/ssh_signing_key`, `tls/server.key`. -- An AWS S3 bucket + object path starting with `/s3/` e.g. `/s3/my-bucket/ssh_signing_key`. You should add an [aws](#aws) config as needed. -- A Google GCS bucket + object path starting with `/gcs/` e.g. `/gcs/my-bucket/ssh_signing_key`. -- A [Vault](https://www.vaultproject.io) path + key starting with `/vault/` e.g. `/vault/secret/cashier/ssh_signing_key`. You should add a [vault](#vault) config as needed. - -Exception to this: the `http_logfile` option **ONLY** writes to local files. - ## server -- `use_tls` : boolean. If this is set then either `tls_key` and `tls_cert` are required, or `letsencrypt_servername` is required. -- `tls_key` : string. Path to the TLS key. See the [note](#a-note-on-files) on files above. -- `tls_cert` : string. Path to the TLS cert. See the [note](#a-note-on-files) on files above. -- `letsencrypt_servername`: string. If set will request a certificate from LetsEncrypt. This should match the expected FQDN of the server. -- `letsencrypt_cachedir`: string. Directory to cache the LetsEncrypt certificate. See the [note](#a-note-on-files) on files above. - `address` : string. IP address to listen on. If unset the server listens on all addresses. - `port` : int. Port to listen on. - `user` : string. User to which the server drops privileges to. **Note** Dropping privileges might not work as expected as some [threads may retain their privileges due to the limitations of the Go runtime](https://github.com/golang/go/issues/1435). - `cookie_secret`: string. Authentication key for the session cookie. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/cookie_secret`. - `csrf_secret`: string. Authentication key for CSRF protection. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/csrf_secret`. - `http_logfile`: string. Path to the HTTP request log. Logs are written in the [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format). The only valid destination for logs is a local file path. -- `require_reason`: bool. Require the client to provide a reason when requesting a certificate. Defaults to `false`. -- `database`: See below. - -### database - -The database is used to record issued certificates for audit and revocation purposes. - -- `type` : string. One of `mysql`, `sqlite` or `mem`. -- `address` : string. (`mysql` only) Hostname and optional port of the database server. -- `username` : string. Database username. -- `password` : string. Database password. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/mysql_password`. -- `filename` : string. (`sqlite` only). Path to sqlite database. -- `dbname`: string (`mysql` only). Name of database to use. - -Examples: -``` -server { - database { - type = "mysql" - address = "my-db-host.corp" - username = "user" - password = "passwd" - dbname = "cashier_production" - } - - database { - type = "mem" - } - - database { - type = "sqlite" - filename = "/data/cashier.db" - } -} -``` -Cashierd **will not** create the database for you - you need to create this. On startup cashierd will execute any schema changes. -Obviously you should setup a role user for running in prodution. - -## auth -- `provider` : string. Name of the oauth provider. Valid providers are currently "google", "github" and "gitlab". -- `oauth_client_id` : string. Oauth Client ID. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/oauth_client_id`. -- `oauth_client_secret` : string. Oauth secret. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/oauth_client_secret`. +## github +- `oauth_client_id` : string. Oauth Client ID. +- `oauth_client_secret` : string. Oauth secret. - `oauth_callback_url` : string. URL that the Oauth provider will redirect to after user authorisation. The path is hardcoded to `"/auth/callback"` in the source. -- `provider_opts` : object. Additional options for the provider. -- `users_whitelist` : array of strings. Optional list of whitelisted usernames. If missing, all users of your current domain/organization are allowed to authenticate against cashierd. For Google auth a user is an email address. For GitHub auth a user is a GitHub username. - -### Provider-specific options - -Oauth providers can support provider-specific options - e.g. to ensure organization membership. -Options are set in the `provider_opts` hash. - -Example: - -``` -auth { - provider = "google" - provider_opts { - domain = "example.com" - } -} -``` - -Supported options: - - -| Provider | Option | Notes | -|---------:|-------------:|----------------------------------------------------------------------------------------------------------------------------------------| -| Github | organization | If this is unset then you must whitelist individual users using `users_whitelist`. The oauth client and secrets should be issued by the specified organization. | -| Gitlab | allusers | Allow all valid users to get signed keys. Only allowed if siteurl set. | -| Gitlab | group | If `allusers` and this are unset then you must whitelist individual users using `users_whitelist`. Otherwise the user must be a member of this group. | -| Gitlab | siteurl | Optional. The url of the Gitlab site. Default: `https://gitlab.com/` | -| Google | domain | If this is unset then you must whitelist individual email addresses using `users_whitelist`. | -| Microsoft | groups | Comma separated list of valid groups. | -| Microsoft | tenant | The domain name of the Office 365 account. | +- `users_whitelist` : array of strings. Optional list of whitelisted GitHub usernames. +- `orgs_whitelist` : array of strings. Optional list of whitelisted GitHub orgs. ## ssh - `signing_key`: string. Path to the certificate signing ssh private key. Use `ssh-keygen` to create the key and store it somewhere safe. See also the [note](#a-note-on-files) on files above. @@ -192,21 +99,6 @@ Supported options: - `max_age`: string. If set the server will not issue certificates with an expiration value longer than this, regardless of what the client requests. Must be a valid Go [`time.Duration`](https://golang.org/pkg/time/#ParseDuration) string. - `permissions`: array of string. Specify the actions the certificate can perform. See the [`-O` option to `ssh-keygen(1)`](http://man.openbsd.org/OpenBSD-current/man1/ssh-keygen.1) for a complete list. e.g. `permissions = ["permit-pty", "permit-port-forwarding", force-command=/bin/ls", "source-address=192.168.0.0/24"]` -## aws -AWS configuration is only needed for accessing signing keys stored on S3, and isn't totally necessary even then. -The S3 client can be configured using any of [the usual AWS-SDK means](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk) - environment variables, IAM roles etc. -It's strongly recommended that signing keys stored on S3 be locked down to specific IAM roles and encrypted using KMS. - -- `region`: string. AWS region the bucket resides in, e.g. `us-east-1`. -- `access_key`: string. AWS Access Key ID. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/aws_access_key`. -- `secret_key`: string. AWS Secret Key. This can be a secret stored in a [vault](https://www.vaultproject.io/) using the form `/vault/path/key` e.g. `/vault/secret/cashier/aws_secret_key`. - -## vault -Vault support is currently a work-in-progress. - -- `address`: string. URL to the vault server. -- `token`: string. Auth token for the vault. - # Usage Cashier comes in two parts, a [cli](cmd/cashier) and a [server](cmd/cashierd). The server is configured using a HCL configuration file - [example](example-server.conf). @@ -248,22 +140,6 @@ TrustedUserCAKeys /etc/ssh/ca.pub ``` where `/etc/ssh/ca.pub` contains the public part of your signing key. -If you wish to use certificate revocation you need to set the `RevokedKeys` option in sshd_config - see the next section. - -## Revoking certificates -When a certificate is signed a record is kept in the configured database. You can view issued certs at `http(s):///admin/certs` and also revoke them. -The revocation list is served at `http(s):///revoked`. To use it your sshd_config must have `RevokedKeys` set: -``` -RevokedKeys /etc/ssh/revoked_keys -``` -See the [`RevokedKeys` option in the sshd_config man page](http://man.openbsd.org/OpenBSD-current/man5/sshd_config) for more. -Keeping the revoked list up to date can be done with a cron job like: -``` -*/10 * * * * * curl -s -o /etc/ssh/revoked_keys https://sshca.example.com/revoked -``` - -Remember that the `revoked_keys` file **must** exist and **must** be readable by the sshd or else all ssh authentication will fail. - # Future Work - Host certificates - only user certificates are supported at present. diff --git a/example-server.conf b/example-server.conf index 795acc5..52d534d 100644 --- a/example-server.conf +++ b/example-server.conf @@ -10,15 +10,12 @@ server { } # Oauth2 configuration -auth { - provider = "google" # Oauth provider to use +github { oauth_client_id = "nnnnnnnnnnnnnnnn.apps.googleusercontent.com" # Oauth client ID oauth_client_secret = "yyyyyyyyyyyyyyyyyyyyyy" # Oauth client secret oauth_callback_url = "https://sshca.example.com/auth/callback" # Oauth callback url - provider_opts { - domain = "example.com" # Oauth-provider specific options - } - users_whitelist = ["marco@gmail.com", "niall@gmail.com", "patrick@gmail.com"] # Optional + users_whitelist = ["marco@gmail.com", "niall@gmail.com", "patrick@gmail.com"] + orgs_whitelist = ["org1", "org2"] } # Configuration for the certificate signer. @@ -28,17 +25,3 @@ ssh { max_age = "720h" # Maximum lifetime of a ssh certificate permissions = ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc", "force-command=/bin/ls"] # Permissions associated with a certificate } - -# Optional AWS config. if an aws config is present, then files (e.g. signing key or tls cert) can be read from S3 using the syntax `/s3/bucket/path/to/signing.key`. -# These can also be set configured using the standard aws-sdk environment variables, IAM roles etc. https://github.com/aws/aws-sdk-go/wiki/configuring-sdk -aws { - region = "eu-west-1" - access_key = "abcdef" - secret_key = "xyz123" -} - -# Optional Vault config. If a vault config is present then files (e.g. signing key or tls cert) can be read from a vault server using the syntax `/vault/secret/service/key_name`. -vault { - address = "https://127.0.0.1:8200" - token = "83f01274-c6f0-4dae-aab9-13a6fc62772e" -} -- cgit v1.2.3